//////////////////////////////////////////////////////////////////////////
//
// This file is a part of XORT project.
//
// PROJECT:         Xort compiler execution subsystem
// FILE:            xbuild.cpp
// AUTHOR:          Vladimir Gumenuk
// DATE:            10-Dec-2012
// MODIFICATIONS:
//
//
//     Copyright(c) 2014, Vladimir Gumenuk
//     All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification, 
//     are permitted provided that the following conditions are met :
//
//     1. Redistributions of source code must retain the above copyright notice, 
//        this list of conditions and the following disclaimer.
//     2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
//        the following disclaimer in the documentation and / or other materials provided with the distribution.
//     3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse 
//        or promote products derived from this software without specific prior written permission.
//
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
//     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
//     OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
//     THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
//     OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
//     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
//     OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
//     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#include "xort.h"
#include "xruntime.h"
#include "xbuildin.h"
#include "xobject.h"
#include "XortElementImpl.h"

//////////////////////////////////////////////////////////////////////////
// Here is it:
// void DbgPrint( string csFmt, ... );
static int __DbgPrint( XRuntime *pRuntime )
{
    InstructionData retPrim; retPrim.m_bInstrCode = p_ret; retPrim.m_bTypeAddr = t_v; // prepare ret primitive

    XCELLSARRAY& stack = pRuntime->get_Stack();
    const XortElement& sysFrame = stack.top();
    if( sysFrame.bAccess != sys_Frame ) return ERR_INVALID_STACK_SYSFRAME;

    if( sysFrame.bType == 0 ) { // no arguments ??
        return pRuntime->do_ret( retPrim ); // need this call to restore stack frame and to prevent code duplication
    }
    
    dword dwArgsCount = sysFrame.bType;
	if( dwArgsCount == 0 ) return ERR_INVALID_ARG_COUNT;
    dword dwStackBase = pRuntime->get_StackBase();
    XortElement& Obj  = stack.get( dwStackBase - 1 ); // should be at last 1 argument
    if( Obj.bType != t_p ) return ERR_INVALID_TYPE;
    if( !Obj._p ) return ERR_INVALID_ARG;
    
    stringref pcsFmt = (stringref)Obj._p;
    string csFmt = *pcsFmt;

    // Is it a paranoia?
    //dword dwArgTest = x_isfmtstr( csFmt );
    //if( dwArgTest != ( dwArgsCount-1 ) ) return ERR_INVALID_ARG_COUNT;

    for( dword i = 2; i <= dwArgsCount; i++ ) // now the arguments are passed in cdecl cc!
    {
        char szBuf[64];
        XortElement& arg = stack.get( dwStackBase - i );
        switch( arg.bType )
        {
        case t_v: return ERR_INVALID_TYPE;
        case t_s1: csFmt.Replace( "%@", _itoa( arg._s1, szBuf, 10 ), 1 ); break;
        case t_u1: csFmt.Replace( "%@", _itoa( arg._u1, szBuf, 16 ), 1 ); break;
        case t_s2: csFmt.Replace( "%@", _itoa( arg._s2, szBuf, 10 ), 1 ); break;
        case t_u2: csFmt.Replace( "%@", _itoa( arg._u2, szBuf, 16 ), 1 ); break;
        case t_s4: csFmt.Replace( "%@", _itoa( arg._s4, szBuf, 10 ), 1 ); break;
        case t_u4: csFmt.Replace( "%@", _itoa( arg._u4, szBuf, 16 ), 1 ); break;
        case t_s8:
#ifdef BUILD_FOR_PC
            _i64toa_s( arg._s8, szBuf, 64, 10 );
            csFmt.Replace( "%@", szBuf, 1 );
#else
            csFmt.Replace( "%@", l64a( arg._s8 ), 1 );
#endif
            break;
        case t_u8:
#ifdef BUILD_FOR_PC
            _i64toa_s( arg._u8, szBuf, 64, 16 );
            csFmt.Replace( "%@", szBuf, 1 );
#else
            csFmt.Replace( "%@", l64a( arg._u8 ), 1 );
#endif
            break;
        case t_r4: csFmt.Replace( "%@", gcvt( arg._r4, 6,  szBuf ), 1 ); break;
        case t_r8: csFmt.Replace( "%@", gcvt( arg._r8, 12,  szBuf ), 1 ); break;
        case t_p:  
            {
                if( arg._p )
                {
                    dword dwType = arg._p->GetType();
                    if( dwType == t_string ) {
                        stringref p = (string*)arg._p;
                        if( p->IsEmpty() )
                            csFmt.Replace( "%@", "(null)", 1 );
                        else
                            csFmt.Replace( "%@", p->GetBuffer(), 1 );
                    } else if( dwType == t_xelement ) {
                        CXortElementImpl *pElement = (CXortElementImpl *)arg._p;
                        string csVal = pElement->FormatToString();
                        csFmt.Replace( "%@", csVal.GetBuffer(), 1 );
                    } else {
                        csFmt.Replace( "%@", "(unknown object type)", 1 );
                    }
                } else {
                    csFmt.Replace( "%@", "(nullptr)", 1 );
                }
            }
            break;
        default:
            csFmt.Replace( "%@", "(unknown argument type)", 1 );
            break;
        }
    }

    csFmt.Add( "\r\n" );
    OutputDebugString( csFmt ); 
    pRuntime->OutputDebugMessage( csFmt.GetBuffer() );

    return pRuntime->do_ret( retPrim );
}

//////////////////////////////////////////////////////////////////////////
// Returns the number of arguments for the current procedure
// it is implemented to support procedures with unspecifed number of argments, but will work for any procedure
static int __Get_Args_Count( XRuntime *pRT )
{
    InstructionData retPrim; retPrim.m_bInstrCode = p_ret; retPrim.m_bTypeAddr = t_u4; // prepare ret primitive

    XCELLSARRAY& stack = pRT->get_Stack();
    const XortElement& sysFrame = stack.top();
    if( sysFrame.bAccess != sys_Frame ) return ERR_INVALID_STACK_SYSFRAME;

    const XortElement& sysFrCaller = stack.get( sysFrame._asp ); // get stake base of the caller frame
    if( sysFrCaller.bAccess != sys_Frame ) return ERR_INVALID_STACK_SYSFRAME;

    XortElement retVal; retVal.bType = t_u4; retVal.bAccess = usr_VarRW; retVal._u4 = sysFrCaller.bType;
    stack.push( retVal );

    return pRT->do_ret( retPrim );
}

//////////////////////////////////////////////////////////////////////////
// Returns the argument by index
static int __Get_Argument( XRuntime *pRT )
{
    InstructionData retPrim; retPrim.m_bInstrCode = p_ret; retPrim.m_bTypeAddr = t_p; // prepare ret primitive

    XCELLSARRAY& stack = pRT->get_Stack();
    const XortElement& sysFrame = stack.top();
    if( sysFrame.bAccess != sys_Frame ) return ERR_INVALID_STACK_SYSFRAME;

    dword dwArgsCount = sysFrame.bType;
    dword dwStackBase = pRT->get_StackBase();
    XortElement& Index  = stack.get( dwStackBase - dwArgsCount );
    if( Index.bType != t_u4 ) return ERR_INVALID_TYPE;

    const XortElement& sysFrCaller = stack.get( sysFrame._asp ); // get stake base of the caller frame
    if( sysFrCaller.bAccess != sys_Frame ) return ERR_INVALID_STACK_SYSFRAME;
    dword dwNumArgs = sysFrCaller.bType;

    if( Index._u4 > dwNumArgs ) return ERR_INVALID_ARG;
    
    // Return XortElement to the caller!
    SymbolRef symElem = FindType( pRT->get_GlobalSymbolList(), t_xelement );

    CXortElementImpl *pElement = NULL;
    int iRet = pRT->createXObject( symElem, NULL, (xobject**)&pElement );
    if( iRet != XORT_OK )
        return iRet;
    
    if( dwNumArgs > 0 ) // if == 0 - return empty element, in future we need to support some exception throwing mechanism
    {
        dword dwArgIndex = sysFrame._asp - Index._u4 - 1; // return arguments in stdcall or cdecl cc...
        XortElement& arg = stack.get( dwArgIndex );
        if( !( arg.bAccess & usr_VarR ) ) return ERR_INVALID_ACCESS;
        pElement->SetElement( arg );
    } // else just return an invalid object... :(

    XortElement retVal; retVal.bType = t_p; retVal.bAccess = usr_VarRW; retVal.SetValue( (xobjectref)pElement );
    stack.push( retVal );
    
    pElement->Release();

    return pRT->do_ret( retPrim );
}

//////////////////////////////////////////////////////////////////////////
//
typedef int BldInProc( XRuntime *pRT );

typedef struct 
{
    BldInProc  *pfn;
    dword       dwNumArgs;
    dword       bRetType;
} BuildInFuncInfo;

static const BuildInFuncInfo g_buildinProcs[] =
{
    { NULL, 0, t_v },
    { __DbgPrint, 0, t_v },
    { __Get_Args_Count, 0, t_u4 },
    { __Get_Argument, 1, t_xelement },
    { NULL, 0, t_v },
};

int exec_buildin_proc( const SymbolRef Proc, XRuntime *pRuntime )
{
    BldInProc *pfn = g_buildinProcs[ Proc.m_iIndex ].pfn;
    int iRet = pfn( pRuntime );
    return iRet;
}

//////////////////////////////////////////////////////////////////////////
// helper - invokes sys function from dll
dword invoke_sys_proc( dword *pdwStack, dword dwNumArgs, dword dwProcAddr, dword dwCallConv );

//////////////////////////////////////////////////////////////////////////
// Executes native procedure loaded from some system dll
// Assumptions:
//      m_pfnNamtive is valid
//      programmer doesn't cheat us and we've been provided with true declaration of the called procedure
//      programmer provided us with valid arguments
//      for 64 bit architectures this must be updated
int exec_native_proc( const SymbolRef Proc, XRuntime *pRuntime )
{
    if( Proc.m_CC != cc_cdecl && Proc.m_CC != cc_stdcall ) return ERR_CALLCONV_NOT_SUPPORTED;

    XCELLSARRAY& stack = pRuntime->get_Stack();
    const XortElement& sysFrame = stack.top();
    if( sysFrame.bAccess != sys_Frame ) return ERR_INVALID_STACK_SYSFRAME;

    dword dwArgsCount = sysFrame.bType;
    dword dwStackBase = pRuntime->get_StackBase();
    
    // get direct pointer to the first argument
    const XortElement *pStkArg = (XortElement *)stack.ptr( ( Proc.m_dwFlags & f_unspecnumargs ) ?  dwStackBase - 1 : dwStackBase - dwArgsCount );

    SYMBOL_LIST::iterator theIter = Proc.m_listMembers.end();
    theIter--; // skip local scope marker
    symbolref pSymArg = ( theIter == NULL ) ? NULL : (Symbol*)theIter;

#define MAX_CALL_STACK 100
    dword dwStkIdx = MAX_CALL_STACK-1; // I do not believe, that there is system proc with more than 10 args,
    dword dwStk[MAX_CALL_STACK];       // so this will be quite enough

    while( dwArgsCount-- )
    {
        if( pSymArg ) { // if we have symbol argument we can apply additional checking
            byte bType = ( pSymArg->m_dwType > t_p ) ? t_p : (byte)pSymArg->m_dwType;
            if( bType != pStkArg->bType ) return ERR_TYPES_MISMATCH;
            if( !( pStkArg->bAccess & usr_VarRW ) ) return ERR_INVALID_ACCESS;
        }
        
        switch( pStkArg->bType ) // prepare arguments 
        {
        case t_s1: 
        case t_u1: 
        case t_s2: 
        case t_u2: 
        case t_s4: 
        case t_u4: dwStk[dwStkIdx--] = pStkArg->_u4; break;
        case t_s8: 
        case t_u8:
            {
                dwStk[dwStkIdx--] = pStkArg->_u4;
                dwStk[dwStkIdx--] = pStkArg->_ti;
                break;
            }
        case t_r4: // PROBLEM: in printf function floats are passed as doubles. This fix case when argument is specified as float.
            {
                if( pSymArg ) { // this will work at last on 90% of cases
                    dwStk[dwStkIdx--] = pStkArg->_u4;
                } else { // need to convert it to double
                    XortElement xF; xF._r8 = pStkArg->_r4;
                    dwStk[dwStkIdx--] = xF._u4;
                    dwStk[dwStkIdx--] = xF._ti;
                }
                break;
            }
        case t_r8: 
            {
                dwStk[dwStkIdx--] = pStkArg->_u4;
                dwStk[dwStkIdx--] = pStkArg->_ti;
                break;
            }
        case t_p : // XVM supports only 2 complex types for native functions: string and array
            if( pStkArg->_p != NULL )
            {
                xobject *pObj = (xobject*)pStkArg->_p;
                dword dwObjType = pObj->GetType();
                if( dwObjType == t_string ) {
                    dwStk[dwStkIdx--] = (dword)((string*)pObj)->GetBuffer();
                } else {
                    symbolref pSymbol = pObj->get_Symbol();
                    if( pSymbol->m_dwTemplateType == t_array ) { // ok, let's support all arrays ... && pSymbol->m_dwTemplSpecType == t_u1 ) {
                        ByteArray *pArray = (ByteArray *)pObj;
                        if( pArray->allocated() == 0 )
                            return ERR_ZERO_SIZE_ARRAY;
                        
                        dwStk[dwStkIdx--] = (dword)((ByteArray*)pObj)->ptr( 0 );
                    } else 
                        return ERR_UNSUPPORTED_TYPE_FOR_NATIVE_CALL;
                }
            } else {
                dwStk[dwStkIdx--] = 0; // be careful passing NULL to natives
            }
            break;
        default  : return ERR_INVALID_TYPE;
        }

        theIter--;
        pSymArg = ( theIter == NULL ) ? NULL : (Symbol*)theIter;
        if( pSymArg && pSymArg->m_csName.Compare( "$" ) == 0 ) { // handle unspecified number of arguments marker
            pSymArg = NULL;
        }
        if( Proc.m_dwFlags & f_unspecnumargs )
            pStkArg--;
        else
            pStkArg++;
    }

    // the actual number of dwords (32bit arch) copied to hardware stack
    dword dwNumStkDwords = MAX_CALL_STACK - 1 - dwStkIdx; // can be more than arg count, because some parameters can be double or int64...
    // do it!
    dword sRet = invoke_sys_proc( &dwStk[dwStkIdx+1], dwNumStkDwords, (dword)Proc.m_pfnNative, Proc.m_CC );

    InstructionData retPrim; retPrim.m_bInstrCode = p_ret; retPrim.m_bTypeAddr = t_v; // prepare ret primitive
    int iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack

    XortElement retVal;
    if( Proc.m_dwType != t_v ) { // push the result of system procedure if any
        retVal.bType = Proc.m_dwType > t_p ? t_p : Proc.m_dwType;
        retVal.bAccess = usr_VarRW;
        retVal._u4 = sRet;
        stack.push( retVal );
    }
    
    return iRet;
}

//////////////////////////////////////////////////////////////////////////
// This nice thunk setups the hardware stack, calls system procedure 
// and cleans the stack after it. Supported calling conventions: cdecl, stdcall.
dword invoke_sys_proc( dword *pdwStack, dword dwNumArgs, dword dwProcAddr, dword dwCallConv )
{
#ifdef BUILD_FOR_PC
    __asm {
        mov ecx, dwNumArgs
        mov edx, pdwStack
_check_arg:
        cmp ecx, 0
        jz _do_call
        mov eax, [edx]      // push argument to the stack
        push eax
        add edx, 4
        dec ecx
        jmp _check_arg
_do_call:
        mov eax, dwProcAddr
        call eax            // do call, result will be returned in eax

        mov edx, dwCallConv // check the proc calling convention
        cmp edx, cc_stdcall
        jz _get_out         // stdcall proc cleanups stack by itself, result is already in eax

        mov ecx, eax        // temporary store save result in ecx

        mov eax, dwNumArgs  // calculate the number of bytes that we need to drop from the stack
        mov ebx, 4
        mul ebx
        add esp, eax        // restore stack

        mov eax, ecx        // return result

_get_out:

    }
#else
    dword result;
    
    __asm__ __volatile__ ("mov %[dwNumArgs], %%ecx \n"  // mov ecx, dwNumArgs
                          "mov %[pdwStack], %%edx \n"   // mov edx, pdwStack
                          "_check_arg: \n"              // _check_arg:
                          "cmp $0, %%ecx \n"            // cmp ecx, 0
                          "jz _do_call \n"              // jz _do_call
                          "mov (%%edx), %%eax \n"       // mov eax, [edx]      // push argument to the stack
                          "push %%eax \n"               // push eax
                          "add $4, %%edx \n"            // add edx, 4
                          "dec %%ecx \n"                // dec ecx
                          "jmp _check_arg \n"           // jmp _check_arg
                          "_do_call: \n"                // _do_call:
                          "mov %[dwProcAddr], %%eax \n" // mov eax, dwProcAddr
                          "call *%%eax \n"              // call eax            // do call, result will be returned in eax
                          
                          "mov %[dwCallConv], %%edx \n" // mov edx, dwCallConv // check the proc calling convention
                          "cmpl $4, %%edx \n"             // cmp edx, cc_stdcall // 4
                          "jz _get_out \n"              // jz _get_out         // stdcall proc cleanups stack by itself, result is already in eax
                          
                          "mov %%eax, %%ecx \n"         // mov ecx, eax        // temporary store save result in ecx
                          
                          "mov %[dwNumArgs], %%eax \n"  // mov eax, dwNumArgs  // calculate the number of bytes that we need to drop from the stack
                          "mov $4, %%ebx \n"            // mov ebx, 4
                          "mul %%ebx \n"                // mul ebx
                          "add %%eax, %%esp \n"         // add esp, eax        // restore stack
                          
                          "mov %%ecx, %%eax \n"         // mov eax, ecx        // return result
                          
                          "_get_out: \n"                // _get_out:
                          "mov %%eax, %[result]\n"      //
                          
                          : [result] "+m" (result)
                          : [pdwStack] "m" (pdwStack), [dwNumArgs] "m" (dwNumArgs), [dwProcAddr] "m" (dwProcAddr), [dwCallConv] "m" (dwCallConv)
                          : "ecx", "edx", "eax", "ebx", "cc"
                          );
    
    return result;
#endif
}
